
#include <iostream>
#include <sstream>

#include "bocat/common/base64.hpp"
#include "bocat/common/time.hpp"
#include "bocat/common/logging.hpp"

#include "record.hpp"
#include "indexiter.hpp"
#include "aubin.hpp"

namespace   sancab   {

Aubin::Aubin( const std::string& data_root, const std::string& index_file, DbEnv* dbenv_p, u_int32_t n_tables, int slim )
        : _data_root( data_root ),
		  _dbenv_p( dbenv_p ),
		  _bin_index( index_file, dbenv_p, n_tables ),
		  _filecell( data_root, slim )
{
}

Aubin::~Aubin()
{
}

///////////////////////////////////////////////////////////////////////
//


int 
Aubin::insert( const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	prev_loc;
	RecordLocation	loc;
	try {
		_bin_index.get( kpin, skey, key, loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. if key exists, delete the old one
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	if ( !loc.is_null() ) {
		_filecell.erase( skey_b64, kpin_b64, key, loc );
	}

    // STEP. insert the record
	//       . put data into file cell
	//       . put location into index
	loc.clear();
	_filecell.put( skey_b64, kpin_b64, key, data, loc, prev_loc );
	_bin_index.put( kpin, skey, key, loc );

    return  0;
}


int 
Aubin::insertAppend( const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	prev_loc;
	try {
		_bin_index.get( kpin, skey, key, prev_loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. insert the record
	//       . put data into file cell
	//       . put location into index
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	RecordLocation	loc;
	_filecell.put( skey_b64, kpin_b64, key, data, loc, prev_loc );
	_bin_index.put( kpin, skey, key, loc );

    return  0;
}


int 
Aubin::insertIgnore( const std::string& kpin,
         const std::string& key,
         const std::string& data )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	loc;
	try {
		_bin_index.get( kpin, skey, key, loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. if key exists, ignore it
	if ( !loc.is_null() ) {
		return 0;
	}

    // STEP. insert the record
	//       . put data into file cell
	//       . put location into index
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	RecordLocation	prev_loc;
	loc.clear();
	_filecell.put( skey_b64, kpin_b64, key, data, loc, prev_loc );
	_bin_index.put( kpin, skey, key, loc );


    return  0;
}



int 
Aubin::find( const std::string& kpin,
         const std::string& key,
               std::string& data )
{   
	boost::mutex::scoped_lock   _scoped_lock( _mutex_read );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	loc;
	try {
		_bin_index.get( kpin, skey, key, loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. if key does NOT exist, just return
	if ( loc.is_null() ) {
		return 0;
	}

    // STEP. get the record
	//       . get data from file cell
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	RecordLocation	prev_loc;
	_filecell.get( skey_b64, kpin_b64, key, data, loc, prev_loc );

    return  data.size();
}

int 
Aubin::find( const std::string& kpin,
         const std::string& key,
               std::vector< std::string >& all_data,
			   u_int32_t limit )
{   
	boost::mutex::scoped_lock   _scoped_lock( _mutex_read );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	loc;
	try {
		_bin_index.get( kpin, skey, key, loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. if key does NOT exist, just return
	if ( loc.is_null() ) {
		return -1;
	}

    // STEP. get the record
	//       . get data from file cell
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	while ( true ) {
		std::string	data;
		RecordLocation	prev_loc;
		_filecell.get( skey_b64, kpin_b64, key, data, loc, prev_loc );
		all_data.push_back( data );
		if ( prev_loc.is_null() ) {
			break;
		}
		loc = prev_loc;

		if ( (limit > 0) && (limit <= all_data.size()) ) {
			break;
		}
	}

    return  all_data.size();
}


int 
Aubin::erase( const std::string& kpin,
           const std::string& key )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

    // STEP. check if key exists
	std::string	skey = getSkeyByKpin( kpin );
	RecordLocation	loc;
	try {
		_bin_index.get( kpin, skey, key, loc );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for key '" << key << "' -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. if key does NOT exist, just return
	if ( loc.is_null() ) {
		return 0;
	}

    // STEP. erase the record
	//       . erase data from file cell
	//       . erase index from file cell
	std::string	skey_b64 = bocat::common::base64_encode( skey );
	std::string kpin_b64 = bocat::common::base64_encode( kpin );

	_filecell.erase( skey_b64, kpin_b64, key, loc );
	_bin_index.erase( kpin, skey, key );

    return  0;
}


///////////////////////////////////////////////////////////////////////
//

int
Aubin::insertAppend( const std::vector< std::string >& kpin_v,
					 const std::vector< std::string >& key_v,    
					 const std::vector< std::string >& data_v )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

	std::vector< Record >	record_v;

	// STEP. prepare
	for (size_t k=0; k<key_v.size(); ++k) {
		Record	r;
		r.kpin = kpin_v[ k ];
		r.key  = key_v[ k ];
		r.data = data_v[ k ];

		r.skey = getSkeyByKpin( r.kpin );

		record_v.push_back( r );
	}

    // STEP. check if key exists
	try {
		_bin_index.get( record_v );
	}
	catch( std::exception& e ) {
		std::ostringstream	msg;
		msg << "fail to check existence for keys -- " << e.what();
		throw std::runtime_error( msg.str() );
	}

    // STEP. insert the record
	//       . put data into file cell
	//       . put location into index
	for (size_t k=0; k<key_v.size(); ++k) {
		Record&	r = record_v[ k ];
		r.skey_b64 = bocat::common::base64_encode( r.skey );
		r.kpin_b64 = bocat::common::base64_encode( r.kpin );

		r.prev_loc = r.loc;
		r.loc.clear();
	}

	_filecell.append( record_v );
	_bin_index.put( record_v );

    return  0;
}

///////////////////////////////////////////////////////////////////////
//


int
Aubin::slim( void )
{
	boost::mutex::scoped_lock   _scoped_lock( _mutex );

	IndexIterator*	it_p = NULL;
	int		count_slimmed = 0;

	bocat::common::Logger*	logger_p = bocat::common::getLogger();

	while( (it_p = _filecell.getSlimIndexIterator()) != NULL ) {
		try {
			//std::cout << bocat::common::now() << " slimming " << _filecell.getSlimmingFile() << " ...\n";
			logger_p->info(__LINE__, __FILE__, " slimming %s ...", _filecell.getSlimmingFile().c_str());

			int	rc = _bin_index.slim( *it_p, _filecell.getSlimmingFileNo() );
			if ( rc > 0 ) {
				count_slimmed += rc;
			}
			//std::cout << bocat::common::now() << " slim " << _filecell.getSlimmingFile() << " done (" << rc << " records slimmed).\n";
			logger_p->info(__LINE__,__FILE__," slim %s done (%d records slimmed).", _filecell.getSlimmingFile().c_str(), rc);
		}
		catch( std::exception& e ) {
			logger_p->info(__LINE__,__FILE__," slim %s failed.", _filecell.getSlimmingFile().c_str());
			logger_p->error(__LINE__,__FILE__," slim exception : %s", e.what());
			logger_p->error(__LINE__,__FILE__," slim %s failed.", _filecell.getSlimmingFile().c_str());
			//std::cout << bocat::common::now() << " slim " << _filecell.getSlimmingFile() << " failed.\n";
			//std::cerr << bocat::common::now() << e.what() << "\n";
			//std::cerr << bocat::common::now() << " slim " << _filecell.getSlimmingFile() << " failed.\n";

			_filecell.freeSlimIndexIterator( it_p, false );
			continue;
		}

		_filecell.freeSlimIndexIterator( it_p );
	}

	return	count_slimmed;
}

///////////////////////////////////////////////////////////////////////
//


const std::string   
Aubin::getSkeyByKpin( const std::string& kpin )
{
	char	skey[12];
	u_int32_t	skey_size;

	memset(skey, 0, sizeof(skey));
	skey_size = (kpin.size() > sizeof(skey)) ? sizeof(skey) : kpin.size();
	memcpy( skey, kpin.c_str() + (kpin.size() - skey_size), skey_size );

	return	std::string( skey, skey_size );
}


///////////////////////////////////////////////////////////////////////
//


}   //// namespace sancab

